﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using OSGeo.MapGuide;
using System.IO;
using System.Xml;

namespace MapViewer
{
    public partial class MgLegend : UserControl, IMapLegend
    {
        // TODO:
        // 
        // 1. This should build the full client-side tree (w/ applicable scales) ala. legend.aspx in the AJAX viewer
        // 2. This should be done in a background worker to avoid clogging up the UI thread.
        // 3. Instead of refreshing the legend on save, just flip visibility of the client-side tree
        // 4. Load layer icons on demand.

        const string IMG_BROKEN = "lc_broken";
        const string IMG_DWF = "lc_dwf";
        const string IMG_GROUP = "lc_group";
        const string IMG_RASTER = "lc_raster";
        const string IMG_SELECT = "lc_select";
        const string IMG_THEME = "lc_theme";
        const string IMG_UNSELECT = "lc_unselect";

        private MgRenderingService _renderSvc;
        private MgResourceService _resSvc;

        public MgLegend()
        {
            InitializeComponent();
        }

        private MgMapBase _map;

        public void Init(MgMapBase map)
        {
            _map = map;
            if (_renderSvc == null)
                _renderSvc = MgServiceFactory.CreateRenderingService();
            if (_resSvc == null)
                _resSvc = MgServiceFactory.CreateResourceService();
            RefreshLegend();
        }

        private Dictionary<string, MgLayerBase> _layers = new Dictionary<string, MgLayerBase>();
        private Dictionary<string, MgLayerGroup> _groups = new Dictionary<string, MgLayerGroup>();
        private Dictionary<string, string> _layerDefinitionContents = new Dictionary<string, string>();

        public void RefreshLegend()
        {
            if (_noUpdate)
                return;

            System.Diagnostics.Trace.TraceInformation("MgLegend.RefreshLegend()");
            var scale = _map.ViewScale;
            var groups = _map.GetLayerGroups();
            var layers = _map.GetLayers();

            ResetTreeView();

            _layerDefinitionContents.Clear();
            _layers.Clear();
            _groups.Clear();

            //Process groups first
            List<MgLayerGroup> remainingNodes = new List<MgLayerGroup>();
            for (int i = 0; i < groups.GetCount(); i++)
            {
                var group = groups.GetItem(i);
                _groups.Add(group.GetObjectId(), group);
                if (!group.GetDisplayInLegend())
                    continue;

                //Add ones without parents first.
                if (group.Group != null)
                {
                    remainingNodes.Add(group);
                }
                else
                {
                    var node = CreateGroupNode(group);
                    trvLegend.Nodes.Add(node);
                }

                while (remainingNodes.Count > 0)
                {
                    List<MgLayerGroup> toRemove = new List<MgLayerGroup>();
                    for (int j = 0; j < remainingNodes.Count; j++)
                    {
                        var parentId = remainingNodes[j].Group.GetObjectId();
                        var nodes = trvLegend.Nodes.Find(parentId, false);
                        if (nodes.Length == 1)
                        {
                            var node = CreateGroupNode(remainingNodes[j]);
                            nodes[0].Nodes.Add(node);
                            toRemove.Add(remainingNodes[j]);
                        }
                    }
                    //Whittle down this list
                    if (toRemove.Count > 0)
                    {
                        foreach (var g in toRemove)
                        {
                            remainingNodes.Remove(g);
                        }
                    }
                }
            }

            //Now process layers
            for (int i = 0; i < layers.GetCount(); i++)
            {
                var lyr = layers.GetItem(i);
                var ldfId = lyr.LayerDefinition;

                if (!_layerDefinitionContents.ContainsKey(ldfId.ToString()))
                {
                    _layerDefinitionContents[ldfId.ToString()] = string.Empty;
                }
            }

            //Collect all resource contents in a batch
            MgStringCollection layerIds = new MgStringCollection();
            foreach (var lid in _layerDefinitionContents.Keys)
            {
                layerIds.Add(lid);
            }

            MgStringCollection layerContents = _resSvc.GetResourceContents(layerIds, null);
            for (int i = 0; i < layerIds.GetCount(); i++)
            {
                string lid = layerIds.GetItem(i);
                _layerDefinitionContents[lid] = layerContents.GetItem(i);
            }
            
            List<MgLayerBase> remainingLayers = new List<MgLayerBase>();
            for (int i = 0; i < layers.GetCount(); i++)
            {
                var layer = layers.GetItem(i);
                _layers.Add(layer.GetObjectId(), layer);

                if (!layer.GetDisplayInLegend())// || !layer.GetVisible()) //GetVisible() indicates potential visibility
                    continue;

                //Add ones without parents first.
                if (layer.Group != null)
                {
                    remainingLayers.Add(layer);
                }
                else
                {
                    var node = CreateLayerNode(layer);
                    if (node != null)
                        trvLegend.Nodes.Add(node);
                }

                while (remainingLayers.Count > 0)
                {
                    List<MgLayerBase> toRemove = new List<MgLayerBase>();
                    for (int j = 0; j < remainingLayers.Count; j++)
                    {
                        var parentId = remainingLayers[j].Group.GetObjectId();
                        var nodes = trvLegend.Nodes.Find(parentId, false);
                        if (nodes.Length == 1)
                        {
                            var node = CreateLayerNode(remainingLayers[j]);
                            if (node != null)
                                nodes[0].Nodes.Add(node);
                            toRemove.Add(remainingLayers[j]);
                        }
                    }
                    //Whittle down this list
                    if (toRemove.Count > 0)
                    {
                        foreach (var g in toRemove)
                        {
                            remainingLayers.Remove(g);
                        }
                    }
                }
            }

            //Now expand any relevant groups
            for (int i = 0; i < groups.GetCount(); i++)
            {
                var group = groups.GetItem(i);
                if (group.ExpandInLegend)
                {
                    var nodes = trvLegend.Nodes.Find(group.GetObjectId(), false);
                    if (nodes.Length == 1)
                    {
                        nodes[0].Expand();
                    }
                }
            }
        }

        private void ResetTreeView()
        {
            trvLegend.Nodes.Clear();
            imgLegend.Images.Clear();

            imgLegend.Images.Add(IMG_BROKEN, Properties.Resources.lc_broken);
            imgLegend.Images.Add(IMG_DWF, Properties.Resources.lc_dwf);
            imgLegend.Images.Add(IMG_GROUP, Properties.Resources.lc_group);
            imgLegend.Images.Add(IMG_RASTER, Properties.Resources.lc_raster);
            imgLegend.Images.Add(IMG_SELECT, Properties.Resources.lc_select);
            imgLegend.Images.Add(IMG_THEME, Properties.Resources.lc_theme);
            imgLegend.Images.Add(IMG_UNSELECT, Properties.Resources.lc_unselect);
        }

        private TreeNode CreateLayerNode(MgLayerBase layer)
        {
            var node = new TreeNode();
            node.Name = layer.GetObjectId();
            node.Text = layer.GetLegendLabel();
            node.Checked = layer.IsVisible();
            var lt = layer.GetLayerType();
            var fsId = layer.GetFeatureSourceId();

            if (fsId.EndsWith("DrawingSource"))
            {
                node.SelectedImageKey = node.ImageKey = IMG_DWF;
            }
            else
            {
                string layerData = null;
                var ldfId = layer.LayerDefinition;
                if (_layerDefinitionContents.ContainsKey(ldfId.ToString()))
                    layerData = _layerDefinitionContents[ldfId.ToString()];

                if (layerData == null)
                    return null;

                XmlDocument doc = new XmlDocument();
                doc.LoadXml(layerData);
                int type = 0;
                XmlNodeList scaleRanges = doc.GetElementsByTagName("VectorScaleRange");
                if (scaleRanges.Count == 0)
                {
                    scaleRanges = doc.GetElementsByTagName("GridScaleRange");
                    if (scaleRanges.Count == 0)
                    {
                        scaleRanges = doc.GetElementsByTagName("DrawingLayerDefinition");
                        if (scaleRanges.Count == 0)
                            return null;
                        type = 2;
                    }
                    else
                        type = 1;
                }

                String[] typeStyles = new String[] { "PointTypeStyle", "LineTypeStyle", "AreaTypeStyle", "CompositeTypeStyle" };
                String[] ruleNames = new String[] { "PointRule", "LineRule", "AreaRule", "CompositeRule" };

                for (int sc = 0; sc < scaleRanges.Count; sc++)
                {
                    XmlElement scaleRange = (XmlElement)scaleRanges[sc];
                    XmlNodeList minElt = scaleRange.GetElementsByTagName("MinScale");
                    XmlNodeList maxElt = scaleRange.GetElementsByTagName("MaxScale");
                    String minScale, maxScale;
                    minScale = "0";
                    maxScale = "1000000000000.0";   // as MDF's VectorScaleRange::MAX_MAP_SCALE
                    if (minElt.Count > 0)
                        minScale = minElt[0].ChildNodes[0].Value;
                    if (maxElt.Count > 0)
                        maxScale = maxElt[0].ChildNodes[0].Value;
                    
                    if (type != 0)
                        break;

                    for (int ts = 0; ts < typeStyles.Length; ts++)
                    {
                        XmlNodeList typeStyle = scaleRange.GetElementsByTagName(typeStyles[ts]);
                        for (int st = 0; st < typeStyle.Count; st++)
                        {
                            // We will check if this typestyle is going to be shown in the legend
                            XmlNodeList showInLegend = ((XmlElement)typeStyle[st]).GetElementsByTagName("ShowInLegend");
                            if (showInLegend.Count > 0)
                                if (!bool.Parse(showInLegend[0].ChildNodes[0].Value))
                                    continue;   // This typestyle does not need to be shown in the legend

                            XmlNodeList rules = ((XmlElement)typeStyle[st]).GetElementsByTagName(ruleNames[ts]);
                            for (int r = 0; r < rules.Count; r++)
                            {
                                XmlElement rule = (XmlElement)rules[r];
                                XmlNodeList label = rule.GetElementsByTagName("LegendLabel");
                                XmlNodeList filter = rule.GetElementsByTagName("Filter");

                                String labelText = "";
                                if (label != null && label.Count > 0 && label[0].ChildNodes.Count > 0)
                                    labelText = label[0].ChildNodes[0].Value;
                                String filterText = "";
                                if (filter != null && filter.Count > 0 && filter[0].ChildNodes.Count > 0)
                                    filterText = filter[0].ChildNodes[0].Value;
                            }
                        }
                    }
                }

                try
                {
                    MgByteReader layerIcon = _renderSvc.GenerateLegendImage(layer.LayerDefinition,
                                                                            _map.ViewScale,
                                                                            16,
                                                                            16,
                                                                            "PNG",
                                                                            -1,
                                                                            -1);
                    if (layerIcon != null)
                    {
                        using (layerIcon)
                        {
                            byte[] b = new byte[layerIcon.GetLength()];
                            layerIcon.Read(b, b.Length);
                            using (var ms = new MemoryStream(b))
                            {
                                string id = Guid.NewGuid().ToString();
                                Image img = Image.FromStream(ms);

                                imgLegend.Images.Add(id, img);
                                node.SelectedImageKey = node.ImageKey = id;
                            }
                            node.Tag = false;
                        }
                    }
                    else
                    {
                        node.SelectedImageKey = node.ImageKey = IMG_BROKEN;
                    }
                }
                catch
                {
                    node.SelectedImageKey = node.ImageKey = IMG_BROKEN;
                }
                finally
                {
                    //HACK-ish: If we have a broken icon, 99% of the it's because this layer is not actually visible.
                    if (node.ImageKey == IMG_BROKEN)
                        node = null;
                }
            }
            
            return node;
        }

        private TreeNode CreateGroupNode(MgLayerGroup group)
        {
            var node = new TreeNode();
            node.Name = group.GetObjectId();
            node.Text = group.GetLegendLabel();
            node.Checked = group.IsVisible();
            node.SelectedImageKey = node.ImageKey = IMG_GROUP;
            node.Tag = true;
            return node;
        }

        private double _scale;

        public void SetScale(double scale)
        {
            _scale = scale;
            RefreshLegend();
        }

        private void trvLegend_AfterCheck(object sender, TreeViewEventArgs e)
        {
            if (Convert.ToBoolean(e.Node.Tag)) //Group
            {
                if (_groups.ContainsKey(e.Node.Name))
                {
                    _groups[e.Node.Name].SetVisible(e.Node.Checked);
                    OnRequestRefresh();
                }
            }
            else //Layer
            {
                if (_layers.ContainsKey(e.Node.Name))
                {
                    _layers[e.Node.Name].SetVisible(e.Node.Checked);
                    OnRequestRefresh();
                }
            }
        }

        private bool _noUpdate = false;

        private void OnRequestRefresh()
        {
            var handler = this.VisibilityChanged;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }

        public event EventHandler VisibilityChanged;
    }
}
